﻿using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace GravatarControl
{
    /// <summary>
    /// Gravatar Image WebControl enables your ASP.NET website to display Gravatar picture with size, rating and default image option.
    /// </summary>
    [AspNetHostingPermission(SecurityAction.LinkDemand, Level=AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level=AspNetHostingPermissionLevel.Minimal)]
    [DefaultProperty("Email")]
    [System.Drawing.ToolboxBitmap(typeof(Gravatar), "Gravatar.bmp")]
    [ToolboxData(@"<{0}:Gravatar runat=""server"" Email=""""></{0}:Gravatar>")]
    public class Gravatar : WebControl
    {
        #region Private members

        private const string BASE_URL = "http://www.gravatar.com/avatar/";
        private const int DEFAULT_SIZE = 80;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the Gravatar class.
        /// </summary>
        public Gravatar()
            : base(HtmlTextWriterTag.Img)
        {
        }

        #endregion

        #region Gravatar Properties

        /// <summary>
        /// Specify a default image URL. There are "special" values that you may pass to this parameter which produce dynamic default images. These are "identicon" "monsterid" and "wavatar". If omitted we will serve up our default image, the blue G.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Description("Specify a default image URL.")]
        public string DefaultImage
        {
            get
            {
                string defaultImage = ViewState["DefaultImage"] as string;

                return defaultImage == null ? "" : defaultImage;
            }

            set
            {
                ViewState["DefaultImage"] = value;
            }
        }

        /// <summary>
        /// The email of the user you want to display the avatar.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Description("The email of the user you want to display the avatar.")]
        public string Email
        {
            get
            {
                string email = ViewState["Email"] as string;

                return email == null ? "" : email;
            }

            set
            {
                if (!Regex.Match(value, @"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$").Success)
                {
                    throw new ArgumentException("The e-mail address is invalid.", "value");
                }

                ViewState["Email"] = value.Trim().ToLower();
            }
        }

        /// <summary>
        /// A rating to the URL of G, PG, R, or X. The default is a rating of G.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue(RatingType.G)]
        [Description("A rating to the URL of G, PG, R, or X.")]
        public RatingType Rating
        {
            get
            {
                object rating = ViewState["Rating"];

                return rating == null ? RatingType.G : (RatingType)rating;
            }
            set
            {
                if (value < RatingType.G || value > RatingType.X)
                {
                    throw new ArgumentOutOfRangeException("value", "This RatingType is out of range.");
                }

                this.ViewState["Rating"] = value;

            }
        }

        /// <summary>
        /// The size of the avatar, between 1 and 512 (pixels). The default size is 80 pixels. 
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue(DEFAULT_SIZE)]
        [Description("The size of the avatar, between 1 and 512 (pixels).")]
        public int Size
        {
            get
            {
                object size = ViewState["Size"];

                return size == null ? DEFAULT_SIZE : (int)size;
            }
            set
            {
                if (value < 1 || value > 512)
                {
                    throw new ArgumentOutOfRangeException("value", "This size is out of range.");
                }

                ViewState["Size"] = value;
            }
        }

        #endregion

        #region Image Properties

        /// <summary>
        /// Gets or sets the alternate text displayed in the Gravatar control when the image is unavailable. Browsers that support the ToolTips feature display this text as a ToolTip.
        /// </summary>
        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("The alternate text displayed when the image is unavailable.")]
        [Description("Image_AlternateText")]
        [Localizable(true)]
        public virtual string AlternateText
        {
            get
            {
                string alternateText = ViewState["AlternateText"] as string;
                
                return alternateText == null ? "" : alternateText;
            }
            set
            {
                ViewState["AlternateText"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the location to a detailed description for the Gravatar image.
        /// </summary>
        [Category("Accessibility")]
        [DefaultValue("")]
        [Description("The location to a detailed description for the Gravatar image.")]
        [Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        [UrlProperty]
        public virtual string DescriptionUrl
        {
            get
            {
                string descriptionUrl = ViewState["DescriptionUrl"] as string;

                return descriptionUrl == null ? "" : descriptionUrl;
            }
            set
            {
                ViewState["DescriptionUrl"] = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the control generates an alternate text attribute for an empty string value.
        /// </summary>
        [Category("Accessibility")]
        [DefaultValue(false)]
        [Description("The value indicating whether the control generates an alternate text attribute for an empty string value.")]
        public virtual bool GenerateEmptyAlternateText
        {
            get
            {
                object generateEmptyAlternateText = ViewState["GenerateEmptyAlternateText"];

                return ((generateEmptyAlternateText != null) && ((bool)generateEmptyAlternateText));
            }
            set
            {
                ViewState["GenerateEmptyAlternateText"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the alignment of the Gravatar image control in relation to other elements on the Web page.
        /// </summary>
        [DefaultValue(0)]
        [Description("The alignment of the Gravatar image control in relation to other elements on the Web page.")]
        [Category("Layout")]
        public virtual ImageAlign ImageAlign
        {
            get
            {
                object imageAlign = ViewState["ImageAlign"];
                
                return imageAlign == null ? ImageAlign.NotSet : (ImageAlign)imageAlign;
            }
            set
            {
                if ((value < ImageAlign.NotSet) || (value > ImageAlign.TextTop))
                {
                    throw new ArgumentOutOfRangeException("value", "This ImageAlign is out of range.");
                }

                ViewState["ImageAlign"] = value;
            }
        }

        #endregion

        #region Private Properties

        private string ImageUrl
        {
            get { return BuildImageUrl(Email); }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Adds the attributes of a Gravatar to the output stream for rendering on the client.
        /// </summary>
        /// <param name="writer">A System.Web.UI.HtmlTextWriter that contains the output stream to render on the client browser.</param>
        protected override void AddAttributesToRender(HtmlTextWriter writer)
        {
            base.AddAttributesToRender(writer);

            string temp = ImageUrl;
            if (temp.Length > 0)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Src, temp);
            }

            temp = DescriptionUrl;
            if (temp.Length != 0)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Longdesc, base.ResolveClientUrl(temp));
            }

            temp = AlternateText;
            if ((temp.Length > 0) || this.GenerateEmptyAlternateText)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Alt, temp);
            }

            if (ImageAlign != ImageAlign.NotSet)
            {
                writer.AddAttribute(HtmlTextWriterAttribute.Align, ImageAlign.ToString().ToLower());
            }
        }

        #endregion

        #region Private Methods

        private string BuildImageUrl(string email)
        {
            StringBuilder imageUrl = new StringBuilder(BASE_URL);

            imageUrl.Append(GenerateMD5Hash(email));
            imageUrl.AppendFormat("?s={0}&r={1}", Size, Rating.ToString().ToLower());

            string defaultImage = DefaultImage;

            if (!String.IsNullOrEmpty(defaultImage))
            {
                imageUrl.AppendFormat("&d={0}", HttpUtility.UrlEncode(defaultImage));
            }

            return imageUrl.ToString();
        }

        private static string GenerateMD5Hash(string email)
        {
            byte[] bytes = Encoding.Default.GetBytes(email);
            byte[] buffer = new MD5CryptoServiceProvider().ComputeHash(bytes);
            StringBuilder hash = new StringBuilder(buffer.Length * 2);

            for (int i = 0; i < buffer.Length; i++)
            {
                hash.Append(buffer[i].ToString("x2"));
            }

            return hash.ToString();
        }

        #endregion
    } 
}
